/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hd.trans.camerax

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.graphics.*
import android.graphics.drawable.ColorDrawable
import android.hardware.display.DisplayManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Size
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import androidx.camera.core.*
import androidx.camera.core.Camera
import androidx.camera.core.ImageCapture.Metadata
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.postDelayed
import androidx.fragment.app.Fragment
import com.google.common.util.concurrent.ListenableFuture
import java.io.ByteArrayOutputStream
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import com.hd.trans.R


/** Helper type alias used for analysis use case callbacks */
typealias LumaListener = LuminosityListener

/**
 * Main fragment for this app. Implements all camera operations including:
 * - Viewfinder
 * - Photo taking
 * - Image analysis
 */
class CameraFragment : Fragment(), LumaListener {

    private lateinit var container: ConstraintLayout
    private lateinit var viewFinder: PreviewView
    private lateinit var focusView: FocusImageView
    //private lateinit var outputDirectory: File

    private var displayId: Int = -1
    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
    private var preview: Preview? = null
    private var imageCapture: ImageCapture? = null
    private var camera: Camera? = null
    private var cameraProvider: ProcessCameraProvider? = null

    private val displayManager by lazy {
        requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
    }

    /** Blocking camera operations are performed using this executor */
    private lateinit var cameraExecutor: ExecutorService

    private lateinit var mAnalysisExecutor: ExecutorService

    private var mCaptureCallback: CaptureCallback? = null

    private var imageAnalysis:ImageAnalysis?=null

    private var scanMask: ScanMaskDrawable? = null

    /**
     * 闪光灯模式
     */
    var lightMode: CameraLightMode = CameraLightMode.CLOSE

    private var lumaListener: LumaListener? = null

    private var analyzerSettings = AnalyzerSettings()

    private var mCropPoints:Array<Point>? = null

    private val mFindCropConfig by lazy {
        FindCropConfig()
    }

    var enableBorderDetect:Boolean
        get() = analyzerSettings.enableBorderDetect
        set(value) {
            analyzerSettings.enableBorderDetect = value
            setBorderDetect(value)
        }

    fun updateLightMode(lightMode: CameraLightMode){
        this.lightMode = lightMode
        camera?.let {
            when(lightMode){
                CameraLightMode.AUTO ->{
                    it.cameraControl.enableTorch(false)
                    imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
                }
                CameraLightMode.OPEN ->{
                    it.cameraControl.enableTorch(false)
                    imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
                }
                CameraLightMode.CLOSE ->{
                    it.cameraControl.enableTorch(false)
                    imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
                }
                CameraLightMode.ALWAYS ->{
                    it.cameraControl.enableTorch(true)
                }
            }
        }
    }


    override fun onResume() {
        super.onResume()
        if(lightMode== CameraLightMode.ALWAYS){
            camera?.cameraControl?.enableTorch(true)
        }
    }

    override fun onPause() {
        super.onPause()
        if(lightMode== CameraLightMode.ALWAYS){
            camera?.cameraControl?.enableTorch(false)
        }
    }


    private fun setBorderDetect(isAutoTakePhoto: Boolean) {
        viewFinder.overlay.clear()
        if (isAutoTakePhoto) {
            viewFinder.overlay.add(scanMask ?: ScanMaskDrawable(requireContext()).also {
                it.bounds = Rect(0, 0, viewFinder.width, viewFinder.height)
                scanMask = it
            })
        } else {
            viewFinder.overlay.clear()
        }
    }

    fun setLuminosityListener(lumaListener: LumaListener?) {
        this.lumaListener = lumaListener
        this.analyzerSettings.luminosityDetect = lumaListener != null
    }


    fun setCaptureCallback(captureCallback: CaptureCallback?) {
        mCaptureCallback = captureCallback
    }


    /**
     * We need a display listener for orientation changes that do not trigger a configuration
     * change, for example if we choose to override config change in manifest or for 180-degree
     * orientation changes.
     */
    private val displayListener = object : DisplayManager.DisplayListener {
        override fun onDisplayAdded(displayId: Int) = Unit
        override fun onDisplayRemoved(displayId: Int) = Unit
        override fun onDisplayChanged(displayId: Int) = view?.let { view ->
            if (displayId == this@CameraFragment.displayId) {
                Log.d(TAG, "Rotation changed: ${view.display.rotation}")
                imageCapture?.targetRotation = view.display.rotation
                //imageAnalyzer?.targetRotation = view.display.rotation
            }
        } ?: Unit
    }


    override fun onDestroyView() {
        super.onDestroyView()

        // Shut down our background executor
        cameraExecutor.shutdown()
        // Unregister the broadcast receivers and listeners
        displayManager.unregisterDisplayListener(displayListener)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? =
        inflater.inflate(R.layout.fragment_camera2, container, false)


    @SuppressLint("MissingPermission")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        container = view as ConstraintLayout
        viewFinder = container.findViewById(R.id.view_finder)
        focusView = container.findViewById(R.id.view_focus)

        // Initialize our background executor
        cameraExecutor = Executors.newSingleThreadExecutor()
        mAnalysisExecutor = Executors.newSingleThreadExecutor()

        // Every time the orientation of device changes, update rotation for use cases
        displayManager.registerDisplayListener(displayListener, null)

//        // Determine the output directory
//        outputDirectory = File("")

        // Wait for the views to be properly laid out
        viewFinder.postDelayed(60) {

            // Keep track of the display in which this view is attached
            displayId = viewFinder.display?.displayId?:-1

            // Build UI controls
            updateCameraUi()

            // Set up the camera and its use cases
            setUpCamera()

            setBorderDetect(enableBorderDetect)
        }
    }

    override fun onLumaChange(luma: Double) {
        if (!isAdded)
            return
        requireActivity().runOnUiThread {
            lumaListener?.onLumaChange(luma)
        }
    }

    /**
     * Inflate camera controls and update the UI manually upon config changes to avoid removing
     * and re-adding the view finder from the view hierarchy; this provides a seamless rotation
     * transition on devices that support it.
     *
     * NOTE: The flag is supported starting in Android 8 but there still is a small flash on the
     * screen for devices that run Android 9 or below.
     */
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)

        // Redraw the camera UI controls
        updateCameraUi()

    }

    /** Initialize CameraX, and prepare to bind the camera use cases  */
    private fun setUpCamera(autoLensFacing: Boolean = true) {
        try {
            val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
            cameraProviderFuture.addListener(Runnable {

                // CameraProvider
                cameraProvider = cameraProviderFuture.get()

                if (autoLensFacing) {
                    // Select lensFacing depending on the available cameras
                    lensFacing = when {
                        hasBackCamera() -> CameraSelector.LENS_FACING_BACK
                        hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
                        else -> throw IllegalStateException("Back and front camera are unavailable")
                    }
                }

                // Build and bind the camera use cases
                try {
                    bindCameraUseCases()
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }

            }, ContextCompat.getMainExecutor(requireContext()))
        }catch (e: java.lang.Exception) {
            e.printStackTrace()
        }catch (e: java.lang.Exception) {
            e.printStackTrace()
        }




    }

    /** Declare and bind preview, capture and analysis use cases */
    @SuppressLint("WrongConstant")
    private fun bindCameraUseCases() {
        val rotation = viewFinder.display.rotation

        // CameraProvider
        val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")

        // CameraSelector
        val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
        val width = viewFinder.measuredWidth
        val height = viewFinder.measuredHeight

        val targetSize = Size(width, height)

        // Preview
        preview = Preview.Builder()
            // We request aspect ratio but no resolution
            //.setTargetAspectRatio(screenAspectRatio)
            // Set initial target rotation
            .setTargetRotation(rotation)
            .setTargetResolution(targetSize)
            .build()


        // ImageCapture
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            // We request aspect ratio but no resolution to match preview config, but letting
            // CameraX optimize for whatever specific resolution best fits our use cases
            //.setTargetAspectRatio(screenAspectRatio)
            // Set initial target rotation, we will have to call this again if rotation changes
            // during the lifecycle of this use case
            .setTargetRotation(rotation)
            .setTargetResolution(targetSize)
            .build()

        val analysisBuild = ImageAnalysis.Builder().setTargetRotation(Surface.ROTATION_0)
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
//            .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
            .setTargetResolution(targetSize)


        imageAnalysis = analysisBuild.build()


        imageAnalysis?.setAnalyzer(
            mAnalysisExecutor,
            LuminosityAnalyzer(this,mFindCropConfig,analyzerSettings).apply {
                onFindCropResult = { result->
                    result.data?.let { pointData ->
                        if (!isAdded) return@let
                        mCropPoints = pointData
                        requireActivity().runOnUiThread {
                            val scaleX = width.toFloat() / result.scanImageWidth
                            val scaleY = height.toFloat() / result.scanImageHeight
                            scanMask?.setCropPoints(pointData.toList(), true, scaleX, scaleY)
                        }
                    }
                }

            }
        )

        // Must unbind the use-cases before rebinding them
        cameraProvider.unbindAll()

        try {
            // A variable number of use-cases can be passed here -
            // camera provides access to CameraControl & CameraInfo

            camera = if(imageAnalysis==null){
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture
                )
            }else{
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture,imageAnalysis
                )
            }

            initCameraListener()

            // Attach the viewfinder's surface provider to preview use case
            preview?.setSurfaceProvider(viewFinder.surfaceProvider)
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

        updateLightMode(lightMode)

    }

    /**
     *  [androidx.camera.core.ImageAnalysisConfig] requires enum value of
     *  [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
     *
     *  Detecting the most suitable ratio for dimensions provided in @params by counting absolute
     *  of preview ratio to one of the provided values.
     *
     *  @param width - preview width
     *  @param height - preview height
     *  @return suitable aspect ratio
     */
    private fun aspectRatio(width: Int, height: Int): Int {
        return AspectRatio.RATIO_4_3
    }


    fun takePhoto(outputDirectory: File) {
        // Get a stable reference of the modifiable image capture use case
        imageCapture?.let { imageCapture ->

            // Create output file to hold the image
            val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)

            // Setup image capture metadata
            val metadata = Metadata().apply {

                // Mirror image when using the front camera
                isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
            }

            // Create output options object which contains file + metadata
            val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
                .setMetadata(metadata)
                .build()




            // Setup image capture listener which is triggered after photo has been taken
            imageCapture.takePicture(
                outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
                    override fun onError(exc: ImageCaptureException) {
                        Log.e(TAG, "Photo capture failed: ${exc.message}", exc)

                        mCaptureCallback?.onCaptureError(exc)
                    }

                    override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                        Log.d(TAG, "Photo capture succeeded: $savedUri")
                        mCaptureCallback?.onCaptureSuccess(photoFile.absolutePath, savedUri)
                    }
                })

            // We can only change the foreground Drawable using API level 23+ API

            // Display flash animation to indicate that photo was captured
            container.postDelayed({
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    container.foreground = ColorDrawable(Color.WHITE)
                }
                container.postDelayed({
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            container.foreground = null
                        }
                    }, ANIMATION_FAST_MILLIS)
            }, ANIMATION_SLOW_MILLIS)
        }
    }


    /** Method used to re-draw the camera UI controls, called every time configuration changes. */
    private fun updateCameraUi() {

    }


    private fun initCameraListener() {
        if (camera == null || camera?.cameraInfo == null || camera?.cameraControl == null)
            return

        val zoomState = camera!!.cameraInfo.zoomState
        val cameraXPreviewViewTouchListener =
            CameraViewTouchListener(
                requireContext()
            )

        cameraXPreviewViewTouchListener.setCustomTouchListener(object :
            CameraViewTouchListener.CustomTouchListener {
            override fun zoom(delta: Float) {
                zoomState.value?.let {
                    val currentZoomRatio = it.zoomRatio
                    camera!!.cameraControl.setZoomRatio(currentZoomRatio * delta)
                }
            }

            override fun click(x: Float, y: Float) {
                //点击对焦
                val factory = viewFinder.meteringPointFactory
                val point = factory.createPoint(x, y)
                val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                    // auto calling cancelFocusAndMetering in 3 seconds
                    .setAutoCancelDuration(3, TimeUnit.SECONDS)
                    .build()
                focusView.startFocus(Point(x.toInt(), y.toInt()))
                val future: ListenableFuture<*> =
                    camera!!.cameraControl.startFocusAndMetering(action)

                context?.let {
                    future.addListener({
                        try {
                            val result = future.get() as FocusMeteringResult
                            if (result.isFocusSuccessful) {
                                focusView.onFocusSuccess()
                            } else {
                                focusView.onFocusFailed()
                            }
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }, ContextCompat.getMainExecutor(it))
                }

            }

            override fun doubleClick(x: Float, y: Float) {
                // 双击放大缩小
                zoomState.value?.let {
                    val currentZoomRatio = it.zoomRatio
                    if (currentZoomRatio > it.minZoomRatio) {
                        camera!!.cameraControl.setLinearZoom(0f)
                    } else {
                        camera!!.cameraControl.setLinearZoom(0.5f)
                    }
                }
            }

            override fun longClick(x: Float, y: Float) {
                //longClick
            }
        })

        viewFinder.setOnTouchListener(cameraXPreviewViewTouchListener)


    }


    /** Returns true if the device has an available back camera. False otherwise */
    private fun hasBackCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
    }

    /** Returns true if the device has an available front camera. False otherwise */
    private fun hasFrontCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
    }

    fun getCamera():Camera? {
        return camera
    }

    /**
     * Our custom image analysis class.
     *
     * <p>All we need to do is override the function `analyze` with our desired operations. Here,
     * we compute the average luminosity of the image by looking at the Y plane of the YUV frame.
     */
    private class LuminosityAnalyzer(
        private var mFragment: CameraFragment,
        val findCropConfig: FindCropConfig,
        val settings: AnalyzerSettings
    ) : ImageAnalysis.Analyzer {

        private var lastLuma = 0.0

        private val mFindCrop = MyFindCrop(findCropConfig)

        var onFindCropResult:((ScanResult)->Unit)? = null

        /**
         * 当前帧数
         */
        private var mCurrentFrameNumber = 0

        /**
         * Analyzes an image to produce a result.
         *
         * <p>The caller is responsible for ensuring this analysis method can be executed quickly
         * enough to prevent stalls in the image acquisition pipeline. Otherwise, newly available
         * images will not be acquired and analyzed.
         *
         * <p>The image passed to this method becomes invalid after this method returns. The caller
         * should not store external references to this image, as these references will become
         * invalid.
         *
         * @param imageProxy image being analyzed VERY IMPORTANT: Analyzer method implementation must
         * call image.close() on received images when finished using them. Otherwise, new images
         * may not be received or the camera may stall, depending on back pressure setting.
         *
         */
        override fun analyze(imageProxy: ImageProxy) {
            if (!mFragment.isAdded) {
                mCurrentFrameNumber = 0
                imageProxy.close()
                return
            }

            if (++mCurrentFrameNumber % MyFindCrop.PER_COUNT_FRAME_NUM == 0) {
                mCurrentFrameNumber = 0
            } else {
                imageProxy.close()
                return
            }

            var bitmapProxy: BitmapProxy? = null
            try {
                bitmapProxy = BitmapProxy(imageProxy)
            }catch (_:Exception){ }

            if (bitmapProxy == null){
                imageProxy.close()
                return
            }

            imageProxy.close()

            if (settings.enableBorderDetect){
                try {
                    onFindCropResult?.invoke(mFindCrop.testCrop(bitmapProxy.bitmap))
                }
                catch (e:Exception){
                    e.printStackTrace()
                }
            }

            if (settings.luminosityDetect){

                val luma = calculateBrightness(bitmapProxy.bitmap)

                if (lastLuma != luma){
                    lastLuma = luma

                    // Call all listeners with new value
                    mFragment.onLumaChange(luma)
                }

            }

            bitmapProxy.recycle()
        }

        /**
         * 计算Bitmap的平均亮度，范围是0（最暗）到255（最亮）
         */
        fun calculateBrightness(bitmap: Bitmap): Double {
            val width = bitmap.width
            val height = bitmap.height
            val size = width * height
            val pixels = IntArray(size)

            // 获取Bitmap的所有像素
            bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
            var sum: Long = 0
            for (pixel in pixels) {
                // 计算灰度值
                val r = pixel shr 16 and 0xff
                val g = pixel shr 8 and 0xff
                val b = pixel and 0xff
                val gray = (r + g + b) / 3
                sum += gray.toLong()
            }

            // 计算平均亮度
            return sum / size.toDouble()
        }

    }

    private class BitmapProxy(image: ImageProxy){
        lateinit var bitmap:Bitmap

        init {
            createBitmap(image)
        }

        fun createBitmap(image: ImageProxy){
            val bmTemp:Bitmap = when(image.format){
                ImageFormat.JPEG,ImageFormat.HEIC -> {
                    val buffer = image.planes[0].buffer
                    val bytes = ByteArray(buffer.remaining())
                    buffer.get(bytes)
                    BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                }
                ImageFormat.YUV_420_888 -> {
                    // 将YUV_420_888转换为ARGB_8888
                    val nv21 = yuv_420_888_to_nv21(image)

                    val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
                    val out = ByteArrayOutputStream()
                    yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out)
                    val imageBytes = out.toByteArray()

                    BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
                }

                else ->{
                    throw Exception("不支持的格式")
                }
            }

            val degree = image.imageInfo.rotationDegrees

            Log.d(FindCrop.TAG, "analyze() called with: image = ${image.width}====${image.height}=====${degree}====${image.format}")


//            bmTemp.copyPixelsFromBuffer(image.planes[0].buffer)


            val matrix = Matrix().apply {
                postRotate(degree.toFloat())
            }

            bitmap = Bitmap.createBitmap(
                bmTemp,
                0,
                0,
                bmTemp.width,
                bmTemp.height,
                matrix,
                true
            )

            bmTemp.recycle()
        }

        private fun yuv_420_888_to_nv21(image: ImageProxy): ByteArray {
            val nv21: ByteArray
            val yBuffer = image.planes[0].buffer
            val uBuffer = image.planes[1].buffer
            val vBuffer = image.planes[2].buffer
            val ySize = yBuffer.remaining()
            val uSize = uBuffer.remaining()
            val vSize = vBuffer.remaining()
            nv21 = ByteArray(ySize + uSize + vSize)
            //U and V are swapped
            yBuffer[nv21, 0, ySize]
            vBuffer[nv21, ySize, vSize]
            uBuffer[nv21, ySize + vSize, uSize]
            return nv21
        }

        fun recycle(){
            bitmap.recycle()
        }

    }

    private class AnalyzerSettings{
        var enableBorderDetect = false
        var luminosityDetect = false
    }

    fun getCropPoints():Array<Point>?{
        return mCropPoints
    }

    /**
     * 切换前后置摄像头
     */
    fun switchFacing() {
        if (lensFacing == CameraSelector.LENS_FACING_BACK && hasFrontCamera()) {
            lensFacing = CameraSelector.LENS_FACING_FRONT
            setUpCamera(false)
        } else if (lensFacing == CameraSelector.LENS_FACING_FRONT && hasBackCamera()) {
            lensFacing = CameraSelector.LENS_FACING_BACK
            setUpCamera(false)
        }
    }

    /**
     * 是否是前置摄像头模式
     * @return Boolean
     */
    fun isFacingFront():Boolean{
        return lensFacing==CameraSelector.LENS_FACING_FRONT
    }

    companion object {

        private const val TAG = "CameraXBasic"
        private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val PHOTO_EXTENSION = ".jpg"
        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0

        /** Helper function used to create a timestamped file */
        private fun createFile(baseFolder: File, format: String, extension: String) =
            File(
                baseFolder, SimpleDateFormat(format, Locale.US)
                    .format(System.currentTimeMillis()) + extension
            )
    }
}
